package com.monkeys.service.impl;

import com.monkeys.Utils.CalculateUtil;
import com.monkeys.constants.AccidentConstants;
import com.monkeys.mapper.AccidentMapper;
import com.monkeys.mapper.ActionMapper;
import com.monkeys.pojo.Accident;
import com.monkeys.pojo.Vehicle;
import com.monkeys.service.*;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.text.DecimalFormat;
import java.util.List;

/**
 * @author KuangTeam
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class SimulateServiceImpl implements SimulateService {

    private final VehicleService vehicleService;
    private final GoodsDemandService goodsDemandService;
    private final DemandsListService demandsListService;
    private final ActionMapper actionMapper;
    private final AccidentMapper accidentMapper;


    //异步正式循环逻辑内容
    @Async("taskExecutor")
    @SneakyThrows
    @Override
    public void simulate(Vehicle vehicle, Double cycleTime, Long second) {
        List<Long> demandsListIdList = CalculateUtil.getDemandsListIdList(demandsListService.getById(vehicle.getDemandsListId()).getDemandsListStr());

        //flag：用于判断0号订单也就是基地，下面两个是保留车辆原始经纬度也就是基地经纬度
        boolean flag = false;
        Double primaryLng = vehicle.getLng();
        Double primaryLat = vehicle.getLat();

        for (Long demandId : demandsListIdList) {
            //订单顺序为 0,**,**,**,0; 当碰到最后一个0.代表回基地，执行逻辑不同需要单独放出来
            //下面两个if就是用于判断开头两个0，第一个0不需要，第二个0需要特殊执行
            if (flag && demandId == 0) {
                simulateGoBack(vehicle, cycleTime, second, primaryLng, primaryLat);
            }

            if (demandId == 0) {
                flag = true;
                continue;
            }

            /*                                           模拟前数据的计算                                               */
            vehicle.setGoodsDemandId(demandId);
            vehicle.setSpeed(60.0);
            //计算一个订单中距离发送方距离和目的地距离（传递车辆信息和订单信息进行计算）
            vehicle.setSenderDistance(goodsDemandService.getSenderDistanceById(vehicle, demandId));
            vehicle.setOwnerDistance(goodsDemandService.getOwnerDistanceById(demandId));
            vehicleService.update(vehicle);
            //估算车辆循环次数
            Double cycleDistance = vehicle.getSpeed() * cycleTime;                      //一次循环前进的距离
            int cycleToSenderNum = (int) (vehicle.getSenderDistance() / cycleDistance);
            int cycleToOwnerNum = (int) (vehicle.getOwnerDistance() / cycleDistance);
            //获取路线关键点经纬度集合（如"34.123 , 110.124"）
            String[] toSenderLngLats = goodsDemandService.getSenderLngLatsById(vehicle, demandId);
            String[] toOwnerLngLats = goodsDemandService.getOwnerLngLatsById(demandId);
            //记录每辆车当前是第几次循环
            int realTimeCycleToSender = 0;
            int realTimeCycleToOwner = 0;
            //记录当前车辆共循环多少次，用于计算行驶时间，用于意外(包括四小时休息)的判断
            int allCycleTimes = 0;

            //模拟时间运行
            Thread.sleep(second * 1000);


            /*                                           正式开始循环                                              */
            while (vehicle.getOwnerDistance() > 0) {
                //开局先进行意外判断
                allCycleTimes++;
                //司机意外判断
                simulateDriverAccident(vehicle, allCycleTimes, second); //传入当前循环次数和一次模拟需要的时间
                //道路意外判断
                if(simulateRouteAccident(vehicle, cycleTime, second)){
                    //如果道路模拟判断为true,代表发生了严重的事故，本辆车本次调度直接结束
                    return;
                }
                //向发送方出发
                if (vehicle.getSenderDistance() > 0) {
                    //更新司机状态
                    vehicle.setActionId(1);
                    //更新距离
                    vehicle.setSenderDistance(vehicle.getSenderDistance() - cycleDistance);
                    //更新经纬度
                    //调用方法获取当前估计的实时经纬度
                    String[] lngLat = CalculateUtil.getRealTimeLngLat(toSenderLngLats, cycleToSenderNum, ++realTimeCycleToSender);
                    // 创建 DecimalFormat 对象，指定保留六位小数的格式
                    DecimalFormat decimalFormat = new DecimalFormat("#.######");
                    vehicle.setLng(Double.valueOf(decimalFormat.format(Double.valueOf(lngLat[0]))));
                    vehicle.setLat(Double.valueOf(decimalFormat.format(Double.valueOf(lngLat[1]))));
                    //最后更新数据到数据库
                    vehicleService.update(vehicle);
                } else {
                    //更新司机状态
                    vehicle.setActionId(1);
                    vehicle.setOwnerDistance(vehicle.getOwnerDistance() - cycleDistance);
                    String[] lngLat = CalculateUtil.getRealTimeLngLat(toOwnerLngLats, cycleToOwnerNum, ++realTimeCycleToOwner);
                    // 创建 DecimalFormat 对象，指定保留六位小数的格式
                    DecimalFormat decimalFormat = new DecimalFormat("#.######");
                    vehicle.setLng(Double.valueOf(decimalFormat.format(Double.valueOf(lngLat[0]))));
                    vehicle.setLat(Double.valueOf(decimalFormat.format(Double.valueOf(lngLat[1]))));
                    //最后更新数据到数据库
                    vehicleService.update(vehicle);
                }

                //一次订单派送完成后修正车辆模拟经纬度移动的误差，重置一下经纬度
                vehicle.setLng(goodsDemandService.getOwnerLngById(demandId));
                vehicle.setLat(goodsDemandService.getOwnerLatById(demandId));

                //模拟时间运行
                Thread.sleep(second * 1000);
            }
        }
    }


    //导入随机意外事件模拟(返回true代表发生了严重事故，不再进行后续的模拟)
    private boolean simulateRouteAccident(Vehicle vehicle, Double cycleTime, Long second) {
        //获取所有意外信息
        List<Accident> accidentList = accidentMapper.getAll();
        //每次循环都有概率触发这些事件
        for (Accident accident : accidentList) {
            Double probability = accident.getProbability();
            if (Math.random() < probability) {
                //如果触发事件则进行相关处理
                return (handleAccident(accident, vehicle, cycleTime, second)); //处理完成后直接返回，避免一次循环多个意外重叠
            }
        }
        return false;
    }


    //对不同的意外进行处理(返回true代表发生了严重事故，不再进行后续的模拟)
    @SneakyThrows
    private boolean handleAccident(Accident accident, Vehicle vehicle, Double cycleTime, Long second) {
        //1-天气原因、2-车辆故障、3.交通拥堵、4.发生车祸
        Long id = accident.getId();
        if (id == 1 || id == 3) {
            //天气原因或者交通拥堵原地等待即可
            vehicle.setActionId(4);       //1:运输中  2:无货休息中  3:运输休息中  4.意外  5严重意外
            vehicleService.update(vehicle);
            Thread.sleep(second * 1000);
        } else if (id == 2) {
            //车辆故障,原地等待修复
            vehicle.setActionId(4);
            vehicleService.update(vehicle);
            Thread.sleep(second * 3000);
        } else if (id == 4) {
            //发生车祸，这里直接放弃运输(拖车将货车运输回基地)
            vehicle.setActionId(5);
            vehicleService.update(vehicle);
            log.info("%clr{red}{有车辆出现严重意外！}");
            simulateGoBack(vehicle, cycleTime, second, vehicle.getLng(), vehicle.getLat());
            return true;
        }
        return false;
    }


    //司机意外和休息的模拟
    @SneakyThrows
    private void simulateDriverAccident(Vehicle vehicle, int allCycleTimes, Long second) {
        //1:运输中  2:无货休息中  3:运输休息中   4.意外
        if (vehicle.getActionId() == 1 && allCycleTimes % 4 == 0) {
            //每达到四次循环也就是四个小时必须进行一小时的休息
            //更新司机状态
            vehicle.setActionId(3);
            vehicleService.update(vehicle);
            Thread.sleep(second * 1000);
        }
        //意外的模拟
        Double accidentRate = actionMapper.getRateByName(AccidentConstants.TRANSPORT_ACCIDENT); //意外概率
        if (Math.random() < accidentRate) {
            //更新司机状态
            vehicle.setActionId(4);
            vehicleService.update(vehicle);
            Thread.sleep(second * 1000);
        }
    }


    //车辆送完订单回到基地的模拟
    @SneakyThrows
    public void simulateGoBack(Vehicle vehicle, Double cycleTime, Long second, Double primaryLng, Double primaryLat) {
        Long demandId = 0L;
        vehicle.setGoodsDemandId(demandId);           //订单号0是代表回到基地
        vehicle.setSpeed(60.0);
        //计算回家的距离
        vehicle.setSenderDistance(goodsDemandService.goBackGetDistance(vehicle, primaryLng, primaryLat));
        vehicle.setOwnerDistance(0.00);
        vehicleService.update(vehicle);

        //估算车辆循环次数
        Double cycleDistance = vehicle.getSpeed() * cycleTime;                      //一次循环前进的距离
        int cycleToSenderNum = (int) (vehicle.getSenderDistance() / cycleDistance);
        //获取路线关键点经纬度集合（如"34.123 , 110.124"）
        String[] toSenderLngLats = goodsDemandService.getSenderLngLatsById(vehicle, primaryLng, primaryLat);
        //记录每辆车当前是第几次循环
        int realTimeCycleToSender = 0;

        //模拟时间运行
        Thread.sleep(second * 1000);

        //开始模拟循环
        while (true) {
            if (vehicle.getSenderDistance() > 0) {
                //更新距离
                vehicle.setSenderDistance(vehicle.getSenderDistance() - cycleDistance);
                //更新经纬度
                //调用方法获取当前估计的实时经纬度
                String[] lngLat = CalculateUtil.getRealTimeLngLat(toSenderLngLats, cycleToSenderNum, ++realTimeCycleToSender);
                // 创建 DecimalFormat 对象，指定保留六位小数的格式
                DecimalFormat decimalFormat = new DecimalFormat("#.######");
                vehicle.setLng(Double.valueOf(decimalFormat.format(Double.valueOf(lngLat[0]))));
                vehicle.setLat(Double.valueOf(decimalFormat.format(Double.valueOf(lngLat[1]))));
                //最后更新数据到数据库
                vehicleService.update(vehicle);
            } else {
                //循环结束，进行一些数据的修正
                vehicle.setLng(primaryLng);
                vehicle.setLat(primaryLat);
                vehicle.setSenderDistance(0.00);
                vehicleService.update(vehicle);
                break;
            }
            //模拟时间运行
            Thread.sleep(second * 1000);
        }
    }
}
